package org.adream.dreamnt;


import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.sql.DataSource;
import javax.validation.constraints.NotNull;

import org.apache.commons.lang3.StringUtils;
import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.jasig.cas.authentication.handler.DefaultPasswordEncoder;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.sensorsdata.analytics.javasdk.SensorsAnalytics;
import com.sensorsdata.analytics.javasdk.exceptions.InvalidArgumentException;

@Component("casCustomAuthenticationHandler")
public class CasCustomAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler
{
    @NotNull
    private String sql;
    
    @Value("data.path.account.userpic")
	String userPicPath;

    @Override
    protected HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException
    {
        if (this.sql.isEmpty() ){
            throw new GeneralSecurityException("Authentication handler (sql) is not configured correctly.");
        }
        
        if (getJdbcTemplate() == null){
            throw new GeneralSecurityException("Authentication handler (getJdbcTemplate()) is not configured correctly.");
        }

        String username = credential.getUsername();
        String password = credential.getPassword();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //神策数据'盒子_登录结果'事件埋点
        Map<String, Object> properties = new HashMap<String, Object>();
        
        //获取 神策匿名用户ID
        String anonymousId = "";
      	Cookie[] cookies = request.getCookies();
      	if(cookies != null && cookies.length > 0) {
      		for (Cookie cookie : cookies){
      		    if(GlobalContext.SENSOR_DATA_COOKIE_KEY.equals(cookie.getName())) {
      		    	String value;
					try {
						value = URLDecoder.decode(cookie.getValue(), "UTF-8");
						Map<String, Object> info = JsonUtil.parseJson(URLDecoder.decode(value, "UTF-8"));
						//先判断first_id键的值,若无数据,取distinct_id的键值,有数据,取first_id的键值
						anonymousId = StringUtils.isEmpty((String) info.get("first_id")) ? (String) info.get("distinct_id") : (String) info.get("first_id");
					} catch (UnsupportedEncodingException e) {
						logger.warn("登录失败," + e.getMessage());
						throw new PreventedException("Invalid arg exception for " + username, e);
					} catch (JoseException e) {
						logger.warn("登录失败," + e.getMessage());
						throw new PreventedException("Invalid arg exception for " + username, e);
					}
      		        break;
      		    }
      		}
      	}   
        //判断登录方式
        if(!StringUtils.isEmpty(username)) {
        	if(Util.verifyPhone(username)) {
        		properties.put("login_type", "手机");
        	} else if(Util.verifyEmail(username)) {
        		properties.put("login_type", "邮箱");        		
        	} else {
        		//先暂时判定为 用户名登录,若为微信登录后面会判断
        		properties.put("login_type", "用户名");        		        		
        	}
        }
        properties.put("is_success", false);
        try
        {
        	int cnt = (this.sql+" ").split("\\?").length-1;
        	Object params[] = new Object[cnt];
        	for(int i=0;i<cnt;i++){
        		params[i] = username;
        	}
        	SqlRowSet srs = getJdbcTemplate().queryForRowSet(this.sql, params);//sql应该有账户过期判断
        	while(srs.next()){
        		String salt = srs.getString("salt");
        		String pwd = srs.getString("password");
        		String method = srs.getString("method");
        		String up;
        		switch(method){
        		case "T" ://token
        			up = password;
        			break;
        		case "M" ://mis
        			up = new DefaultPasswordEncoder("MD5").encode(salt+password);
        			break;
        		case "B" ://adreambox
        			up = new DefaultPasswordEncoder("MD5").encode(new DefaultPasswordEncoder("MD5").encode(password)+salt);
        			break;
        		case "A" ://account
        		default:
        			up = new DefaultPasswordEncoder("MD5").encode(password+salt);
        		}
        		String uid = srs.getString("uid");
        		SqlRowSet loginStateSrs = getJdbcTemplate().queryForRowSet("select * from cas_login_state where uid=? and dr=?", uid, 1);
                
                //查用户登录是否异常
                if(loginStateSrs.next()) {
                	int wrongTimes = loginStateSrs.getInt("wrongTimes");
                	//账密登录&登录错误次数大于10
                	if(!"T".equals(method) && wrongTimes > GlobalContext.MAX_WRONG_TIME) {
                		properties.put("failure_reason", "验证码错误");
                		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
                		throw new UserLoginExceptionException();
                	}
                	Date mts = loginStateSrs.getDate("mts");
                	long now = System.currentTimeMillis();
                	//查看是否处于 '锁定'状态
                	boolean lockedInState = (now - mts.getTime()) < 60 * 60 * 1000 ? true : false;
                	//账密登录&登录错误次数大于5&处于锁定时间内
                	if(!"T".equals(method) && wrongTimes > GlobalContext.LOCK_WRONG_TIME && lockedInState) {
                		properties.put("failure_reason", "该账号已被锁定");
                		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
                		throw new UserLoginLockException();
                	}
                	//登录密码错误次数重置
                	getJdbcTemplate().update("update cas_login_state set wrongTimes=? where uid=?",0,uid);
                }
        		
                if (!pwd.equals(up)) {
                	if(loginStateSrs.next()) {
                		//更新登录密码错误次数
                		int wrongTimes = loginStateSrs.getInt("wrongTimes");
                		if(wrongTimes <= GlobalContext.MAX_WRONG_TIME) {
                			getJdbcTemplate().update("update cas_login_state set wrongTimes=wrongTimes+1 where uid=?", uid);                			
                		}
                	} else {
                		//插入登录异常记录
                		getJdbcTemplate().update("replace into cas_login_state(uid,wrongTimes,dr) VALUES(?,?,?)",uid,1,1);
                	}
                	continue;
                }

                //将前端的匿名ID与用户ID关联
                if(anonymousId!=null&&!"".equals(anonymousId)){
                	Util.saTrackSignUp(uid, anonymousId);
                }
                
                if ("M,B,A".indexOf(method)>-1){
                    //做验证码判断
                	//org.apache.commons.lang3.StringUtils.swapCase(arg0)
            		//如果有验证码 判断一下是否和验证码符合，  A和M加密方式需要验证码校验。
            		if(!checkKaptcha()){
            			properties.put("failure_reason", "验证码错误");
                		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
            			throw new CheckcodeWrongException();
            		}
                }
                credential.setUsername(uid);
                
                //查用户是否被拉黑
                Boolean isBlack = getJdbcTemplate().queryForObject("select is_black from acc_user where uid=?",Boolean.class, uid);
                if(isBlack) {
                	properties.put("failure_reason", "该账号已被拉黑");
            		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
                	throw new BlackListUserException();
                }
                
                //权限判断成功 从session中获取unionid 看是不是从微信登录
                HttpSession session = request.getSession();
                String unionid = (String) session.getAttribute(GlobalContext.WX_UNIONID);
                String nickName = (String) session.getAttribute(GlobalContext.WX_NICKNAME);
                String headimgurl = (String) session.getAttribute(GlobalContext.WX_HEADIMGURL);
                String openId = (String) session.getAttribute(GlobalContext.QQ_OPENID);
                if(!StringUtils.isEmpty(unionid)){//帮用户绑定微信
                	properties.put("login_type", "微信");
                	request.getSession().removeAttribute(GlobalContext.WX_UNIONID);
                	request.getSession().removeAttribute(GlobalContext.WX_NICKNAME);
                	srs = getJdbcTemplate().queryForRowSet("select 1 from acc_user where unionid=?", unionid);
                	if(!srs.next()){
//                		getJdbcTemplate().update("update acc_user set unionid=?,nickName=? where uid=?", unionid, nickName, uid);
                		//获取用户当前的头像
                		String currentHeadimgurl = getJdbcTemplate().queryForObject("select headimgurl from acc_user where uid=?", String.class, uid);
                		//微信头像不为空&用户当前的头像为空
                		if(!StringUtils.isEmpty(headimgurl) && StringUtils.isEmpty(currentHeadimgurl)) {
                			//微信头像作为用户的头像
                			//将微信头像(url)下载到本地
                			String fileName = Util.downloadPic(headimgurl, userPicPath);
                			if(StringUtils.isEmpty(fileName)) {
                				properties.put("failure_reason", "该账号微信图片下载出错");
                        		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
                				throw new DownloadPicException();
                			} else {
                				//设置用户unionid属性
                				Util.saProfileSet(uid, true, "union_id", unionid);
                				getJdbcTemplate().update("update acc_user set unionid=?,nickName=?,headimgurl=? where uid=?", unionid, nickName, fileName, uid);
                			}
                		} else {
                			//设置用户unionid属性
                			Util.saProfileSet(uid, true, "union_id", unionid);
                			getJdbcTemplate().update("update acc_user set unionid=?,nickName=? where uid=?", unionid, nickName, uid);							
						}
                		//更改uname nickName作为uname
                		Integer isChangedUname = getJdbcTemplate().queryForObject("select isChangedUname from acc_user where uid=?", Integer.class, uid);
                		//未更改过uname(uname格式为:手机用户+手机末4位)
                		if(isChangedUname == null || isChangedUname == 0) {
                			//微信nickName作为uname&更改isChangedUname值
                			getJdbcTemplate().update("update acc_user set uname=?,isChangedUname=? where uid=?", nickName,1,uid);
                		}
                	}
                }
                
                if(!StringUtils.isEmpty(openId)) {//帮用户绑定qq
                	request.getSession().removeAttribute(GlobalContext.QQ_OPENID);
                	srs = getJdbcTemplate().queryForRowSet("select 1 from acc_user where qqUnionid=?", openId);
                	if(!srs.next()){
                		getJdbcTemplate().update("update acc_user set qqUnionid=? where uid=?", openId, uid);
                	}
                }
                properties.put("is_success", true);
                properties.put("failure_reason", "");
        		Util.saTrack(uid, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
                return createHandlerResult(credential, this.principalFactory.createPrincipal(uid), null);
        	}
    		//抛出帐号不对前 同样做验证码判断clea
    		if(!checkKaptcha()){
                properties.put("failure_reason", "验证码错误");
        		Util.saTrack(anonymousId, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
    			throw new CheckcodeWrongException();
    		}
            properties.put("failure_reason", "账户名或密码错误");
    		Util.saTrack(anonymousId, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
            throw new FailedLoginException("No user or Password does not match.");
         }
        catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) { 
            	properties.put("failure_reason", "未查询到相关账号");
            	logger.debug("未查询到相关账号");
        		Util.saTrack(anonymousId, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
            	throw new AccountNotFoundException(username + " not found with SQL query.");
            } else {            	
            	properties.put("failure_reason", "查询到多条账号");
            	logger.debug("查询到多条账号");
        		Util.saTrack(anonymousId, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
            	throw new FailedLoginException("Multiple records found for " + username);
            }
        } catch (DataAccessException e) {
        	properties.put("failure_reason", "系统错误");
        	logger.debug("系统错误",e);
    		Util.saTrack(anonymousId, false, GlobalContext.SENSOR_DATA_ADREAMBOX_LOGIN_RESULT, properties);
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
    }
    //验证码校验
    private boolean checkKaptcha(){
    	HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    	String checkcode = request.getParameter("checkcode");
    	String key = null;
    	if(StringUtils.isEmpty(checkcode) 
    			|| (key = (String)request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY))==null
				|| !StringUtils.lowerCase(checkcode).equals(StringUtils.lowerCase(key))){
    		logger.debug("checkcode:");
    		logger.debug(checkcode);
    		logger.debug("key:");
    		logger.debug(key);
			return false;
		}
    	return true;
    }

    /**
     * @param sql: The sql to set.
     */
    @Autowired
    public void setSql(@Value("${cas.jdbc.authn.query.sql}") final String sql)
    {
        this.sql = sql;
    }

    @Override
    @Autowired
    public void setDataSource(@Qualifier("queryDatabaseDataSource") final DataSource dataSource)
    {
        super.setDataSource(dataSource);
    }
}